﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web.Script.Serialization;
using Medianamik.Core;
using Medianamik.Core.DAL;
using Medianamik.Core.DAL.Repositories;
using Medianamik.Core.Logging;
using Medianamik.UI.Web.Shop.Data;
using Medianamik.UI.Web.Shop.Data.Dto;
using Medianamik.UI.Web.Shop.Data.Interfaces;
using Medianamik.UI.Web.Shop.Data.Repositories;
using IClientProfile = Medianamik.UI.Web.Shop.Data.Interfaces.IClientProfile;
using IModel = Medianamik.UI.Web.Shop.Data.Interfaces.IModel;
using IOrderItem = Medianamik.UI.Web.Shop.Data.Interfaces.IOrderItem;
using IProduct = Medianamik.UI.Web.Shop.Data.Interfaces.IProduct;
using ITax = Medianamik.UI.Web.Shop.Data.Interfaces.ITax;

namespace Medianamik.UI.Web.Shop.Services
{
    public interface IOrderService<T, TItem, TShoppingCart, TShoppingCartItem,
            TProduct, TModel, TShoppingCartItemWithModels>
        where T : IOrder<TItem>
        where TItem : IOrderItem
        where TShoppingCartItem : IShoppingCartItem<TModel>
        where TShoppingCart : IShoppingCart<TShoppingCartItem, TModel>
        where TModel : IModel
        where TProduct : IProduct
    {
        T GetCurrentUpdatedOrNewOrder(Guid paymentProviderId, string paymentProviderName);
        T CurrentOrder { get; }
        T Get(Guid orderId);
        void SaveOrder(T order);
        T GetOrderByTransactionId(string transactionId);
        IShoppingCartService<TShoppingCart, TShoppingCartItem,
            TProduct, TModel, TShoppingCartItemWithModels> ShoppingCartService { get; }
    }

    public class OrderService<T, TItem, TShoppingCart, TShoppingCartItem, 
        TProduct, TModel, TShoppingCartItemWithModels, TClientProfile, TTax> :
        IOrderService<T, TItem, TShoppingCart, TShoppingCartItem,
            TProduct, TModel, TShoppingCartItemWithModels>
        where T : class, IOrder<TItem>, new()
        where TItem : IOrderItem, new()
        where TShoppingCart : class, IShoppingCart<TShoppingCartItem, TModel>
        where TShoppingCartItem : IShoppingCartItem<TModel>
        where TModel : IModel
        where TShoppingCartItemWithModels : IShoppingCartItemWithModels<TModel>
        where TProduct : IProduct
        where TClientProfile : IClientProfile
        where TTax : ITax
    {
        private readonly IOrderRepository<T, TItem> _orderRepository;
        private readonly IOrderItemRepository<TItem> _orderItemRepository;
        private readonly INodeRepository _nodeRepository;
        private readonly INodeTypeRepository _nodeTypeRepository;
        private readonly IShoppingCartService<TShoppingCart, TShoppingCartItem, 
            TProduct, TModel, TShoppingCartItemWithModels> _shoppingCartService;
        private readonly IProductRepository<TProduct> _productRepository;
        private readonly IClientProfileRepository<TClientProfile> _clientProfileRepository;
        private readonly IRegionAndTaxService _regionAndTaxService;
        private readonly IShippingService _shippingService;
        public static readonly ILogger Logger = new ObjectFactory<ILogger>().Get("ShopLogger");

        private readonly JavaScriptSerializer _serializer;

        public OrderService(IOrderRepository<T, TItem> orderRepository,
            IOrderItemRepository<TItem> orderItemRepository,
            INodeRepository nodeRepository, 
            INodeTypeRepository nodeTypeRepository,
            IShoppingCartService<TShoppingCart, TShoppingCartItem, TProduct, 
                TModel, TShoppingCartItemWithModels> shoppingCartService,
            IProductRepository<TProduct> productRepository,
            IClientProfileRepository<TClientProfile> clientProfileRepository,
            IRegionAndTaxService regionAndTaxService,
            IShippingService shippingService
            )
        {
            _orderRepository = orderRepository;
            _orderItemRepository = orderItemRepository;
            _nodeRepository = nodeRepository;
            _nodeTypeRepository = nodeTypeRepository;
            _shoppingCartService = shoppingCartService;
            _productRepository = productRepository;
            _clientProfileRepository = clientProfileRepository;
            _regionAndTaxService = regionAndTaxService;
            _shippingService = shippingService;
            _serializer = new JavaScriptSerializer(); 
        }

        public T GetOrderByTransactionId(string transactionId)
        {
            var order = _orderRepository.GetOrderByTransactionId(transactionId);
            order.Items = GetOrderItems(order.NodeId);
            return order;
        }

        public IShoppingCartService<TShoppingCart, TShoppingCartItem, 
            TProduct, TModel, TShoppingCartItemWithModels> ShoppingCartService
        {
            get { return _shoppingCartService; }
        }

        public  INodeRepository NodeRepository
        {
            get { return _nodeRepository; }
        }

        public IOrderRepository<T, TItem> OrderRepository
        {
            get { return _orderRepository; }
        }

        protected virtual T CreateOrder()
        {
            var orderId = Guid.NewGuid();
            return new T
                   {
                       NodeId = orderId,
                       TypeId = TypeIds.Order,
                       Notes = String.Empty,
                       Name = orderId.ToString(),
                       CultureName = string.Empty,
                       ParentNodeId = PageIds.OrderList,
                       PaymentProviderTransactionNumber = string.Empty
                   };
        }

        private void UpdateOrderWithShoppingCart(T order, Guid paymentProviderId, string paymentProviderName)
        {
            var subTotal = ShoppingCartService.GetShoppingCartItemsTotalPrice();
            var shippingPrice = _shippingService.CalculateShippingPrice();
            
            if(shippingPrice != -1)
            {
                var taxes = GetTaxes(subTotal + shippingPrice); 

                order.CultureName = CultureInfo.InvariantCulture.Name;
                order.ClientProfile = CurrentShoppingCart.Client;
                order.PurchaseDate = DateTime.UtcNow;
                order.TransactionId = GetNewTransactionNumber();
                order.SubTotal = subTotal;
                order.Total = subTotal + shippingPrice + taxes.Sum(tax => tax.RoundedTax);
                order.Taxes = _serializer.Serialize(taxes);
                order.Shipping = _serializer.Serialize(CreateShippingDTO(shippingPrice));
                order.PaymentProvider = _serializer.Serialize(new PaymentProviderDTO
                      {
                          Name = paymentProviderName,
                          Id = paymentProviderId
                      });
            }
            else
            {
                throw new Exception("Error with shipping price.");
            }
        }

        protected virtual IList<TItem> CreateOrderItems(Guid orderNodeId)
        {
            return _shoppingCartService.GetShoppingCartItems().Select(
                shoppingCartItem => CreateOrderItem(orderNodeId, shoppingCartItem)).ToList();
        }

        protected virtual TItem CreateOrderItem(Guid orderNodeId, TShoppingCartItemWithModels shoppingCartItem)
        {
            var product = _productRepository.Get(shoppingCartItem.Product.Value);

            return new TItem
            {
                TypeId = TypeIds.OrderItem,
                Quantity = shoppingCartItem.Quantity,
                Price = shoppingCartItem.Price,
                Models = _serializer.Serialize(CreateModelDTOList(shoppingCartItem.Models)),
                Product = _serializer.Serialize(CreateProductDTO(product)),
                ParentNodeId = orderNodeId,
                Order = orderNodeId,
                Name = product.Name
            };
        }

        private IEnumerable<TaxDTO> GetTaxes(double amount)
        {
            return _regionAndTaxService.CalculateTaxes(amount,
                     _clientProfileRepository.Get(CurrentShoppingCart.
                     Client.GetValueOrDefault()).DeliveryRegions);
        }

        protected virtual string GetNewTransactionNumber()
        {
          return  DateTime.Now.Ticks.ToString();
        }

        protected virtual Node GetOrderNode(T order)
        {
            var mapper = new NodeMapper(_orderRepository, _nodeTypeRepository);
            return mapper.MapNode(order, new MappingOptions{ValidateHierarchicalRules = false}).InnerNode;
        }

        protected virtual Node GetShoppingCartNode(TShoppingCart shoppingCart)
        {
            var mapper = new NodeMapper(ShoppingCartService.ShoppingCartRepository, _nodeTypeRepository);
            return mapper.MapNode(shoppingCart, new MappingOptions { ValidateHierarchicalRules = false }).InnerNode;
        }

        protected virtual Node GetOrderItemNode(TItem orderItem)
        {
            var mapper = new NodeMapper(_orderItemRepository, _nodeTypeRepository);
            return mapper.MapNode(orderItem, new MappingOptions { ValidateHierarchicalRules = false }).InnerNode;
        }

        private T _currentOrder;
        public virtual T CurrentOrder
        {
            get
            {
                return _currentOrder ?? (_currentOrder =
                    _orderRepository.Get(CurrentShoppingCart.Order.Value));
            }
        }

        public virtual  TShoppingCart CurrentShoppingCart
        {
            get { return ShoppingCartService.CurrentShoppingCart; }
        }

        public virtual T Get(Guid nodeId)
        {
            return _orderRepository.Get(nodeId);
        }

        public virtual IList<TItem> GetOrderItems(Guid orderNodeId)
        {
            return _orderItemRepository.ChildsOf(orderNodeId, false, 1, CultureInfo.InvariantCulture, 
                ContentState.Draft, ActiveStatus.All);
        }

        private static List<ModelDTO> CreateModelDTOList(IEnumerable<TModel> models)
        {
            var modelsDto = new List<ModelDTO>();

            models.ForEach(model => modelsDto.Add(
               new ModelDTO
               {
                   Id = model.NodeId,
                   Name = model.Name
               }));

            return modelsDto;
        }

        private static ProductDTO CreateProductDTO(TProduct product)
        {
            return new ProductDTO {Name = product.Name, Id = product.NodeId};
        }

        protected virtual ShippingDTO CreateShippingDTO(double shippingPrice)
        {
            return new ShippingDTO
            {
                Name = _shippingService.Name,
                Id = _shippingService.Id,
                Price = shippingPrice
            };
        }

        public T GetCurrentUpdatedOrNewOrder(Guid paymentProviderId, string paymentProviderName)
        {
            T order = null;
            var nodesToSave = new List<Node>();
            var shoppingCartToSave = CurrentShoppingCart;

            if (shoppingCartToSave.ShoppingCartItems.Count > 0 && shoppingCartToSave.Client.HasValue)
            {
                if (shoppingCartToSave.Order.HasValue)
                {
                    order = Get(shoppingCartToSave.Order.Value);
                    OrderRepository.DeleteOrderItems(order);
                }
                else
                {
                    order = CreateOrder();
                    shoppingCartToSave.Order = order.NodeId;
                }

                UpdateOrderWithShoppingCart(order, paymentProviderId, paymentProviderName);
                nodesToSave.Add(GetOrderNode(order));
                order.Items = CreateOrderItems(order.NodeId);
                order.Items.ForEach(item => nodesToSave.Add(GetOrderItemNode(item)));
                nodesToSave.Add(GetShoppingCartNode(shoppingCartToSave));

                NodeRepository.SaveNodes(nodesToSave);
            }
            else
            {
                Logger.Trace("No items in shopping cart or clientprofile is empty.");
                order = null;
            }

            return order;
        }

        public virtual void SaveOrder(T order)
        {
            var mapper = new NodeMapper(_orderRepository, _nodeTypeRepository);
            var mappedNode = mapper.MapNode(order);

            _orderRepository.SaveNode(mappedNode);
        }
    }

    public interface IOrderService: IOrderService<Order, OrderItem, ShoppingCart, ShoppingCartItem,
        BaseProduct, Model, ShoppingCartItemWithModels>
    {
        

    }

    public class OrderService: OrderService<Order, OrderItem, ShoppingCart, ShoppingCartItem,
        BaseProduct, Model, ShoppingCartItemWithModels, ClientProfile, Tax>, IOrderService
    {
        public OrderService(IOrderRepository<Order, OrderItem> orderRepository, 
            IOrderItemRepository<OrderItem> orderItemRepository, 
            INodeRepository nodeRepository, INodeTypeRepository nodeTypeRepository,
            IShoppingCartService<ShoppingCart, ShoppingCartItem, BaseProduct, 
            Model, ShoppingCartItemWithModels> shoppingCartService,
            IProductRepository<BaseProduct> productRepository, 
            IClientProfileRepository<ClientProfile> clientProfileRepository, 
            IRegionAndTaxService regionAndTaxService, IShippingService shippingService) : 
            base(orderRepository, orderItemRepository, nodeRepository, nodeTypeRepository,
            shoppingCartService, productRepository, clientProfileRepository, regionAndTaxService, shippingService)
        {
        }
    }


    public class JsonDTO
    {
        public Guid? Id { get; set; }
        public string Name { get; set; }
    }

    public class ModelDTO : JsonDTO { }

    public class ProductDTO : JsonDTO { }

    public class ShippingDTO : JsonDTO
    {
        public double Price;
    }

    public class PaymentProviderDTO : JsonDTO { }


}